﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="SafObjectDefinitionVisitorEx.cs" company="Collaboris Ltd.">
//   Copyright (c) Collaboris Ltd. All rights Reserved.
// </copyright>
// <summary>
//   Defines the SafObjectDefinitionVisitorEx type.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Collaboris.Saf.Engine.Factory.Spring
{
    using System.Collections;
    using System.Collections.Generic;
    using System.Collections.Specialized;

    using global::Spring.Collections;
    using global::Spring.Objects;
    using global::Spring.Objects.Factory.Config;

    using System;

    using global::Spring.Objects.Factory.Support;
    using global::Spring.Util;

    public class SafObjectDefinitionVisitorEx
    {

        public delegate bool DynamicAssignmentsResolveHandler(string rawPropValue, string propName, string definitionName );

        private readonly DynamicAssignmentsResolveHandler dynamicAssignmentsResolveHandler;

        public SafObjectDefinitionVisitorEx(DynamicAssignmentsResolveHandler dynamicAssignmentsResolveHandler)
        {
            this.dynamicAssignmentsResolveHandler = dynamicAssignmentsResolveHandler;
        }

        /// <summary>
        /// Resolves the given value taken from an object definition according to its type
        /// </summary>
        /// <param name="value">the value to resolve</param>
        /// <returns>the resolved value</returns>
        protected object ResolveValue(object value)
        {
            if (value is IObjectDefinition)
            {
                VisitObjectDefinition((IObjectDefinition)value, null);
            }
            else if (value is ObjectDefinitionHolder)
            {
                VisitObjectDefinition(((ObjectDefinitionHolder)value).ObjectDefinition, ((ObjectDefinitionHolder)value).ObjectName);
            }
            else if (value is RuntimeObjectReference)
            {
                RuntimeObjectReference ror = (RuntimeObjectReference)value;
                //name has to be of string type.
                string newObjectName = ResolveStringValue(ror.ObjectName);
                if (!newObjectName.Equals(ror.ObjectName))
                {
                    return new RuntimeObjectReference(newObjectName);
                }
            }
            else if (value is ManagedList)
            {
                VisitManagedList((ManagedList)value);
            }
            else if (value is ManagedSet)
            {
                VisitManagedSet((ManagedSet)value);
            }
            else if (value is ManagedDictionary)
            {
                VisitManagedDictionary((ManagedDictionary)value);
            }
            else if (value is NameValueCollection)
            {
                VisitNameValueCollection((NameValueCollection)value);
            }
            else if (value is TypedStringValue)
            {
                TypedStringValue typedStringValue = (TypedStringValue)value;
                String stringValue = typedStringValue.Value;
                if (stringValue != null)
                {
                    String visitedString = ResolveStringValue(stringValue);
                    typedStringValue.Value = visitedString;
                }
            }
            else if (value is string)
            {
                return ResolveStringValue((string)value);
            }
            else if (value is ExpressionHolder)
            {
                ExpressionHolder holder = (ExpressionHolder)value;
                string newExpressionString = ResolveStringValue(holder.ExpressionString);
                return new ExpressionHolder(newExpressionString);
            }
            return value;
        }

        /// <summary>
        /// Visits the property values of the ObjectDefinition, replacing string values
        /// using the specified IVariableSource.
        /// </summary>
        /// <param name="objectDefinition">The object definition.</param>
        protected void VisitPropertyValues(IObjectDefinition objectDefinition, string definitionName)
        {
            List<string> propertiesToRemove = new List<string>();
            MutablePropertyValues pvs = objectDefinition.PropertyValues;
            if (pvs != null)
            {
                for (int j = 0; j < pvs.PropertyValues.Length; j++)
                {
                    PropertyValue pv = pvs.PropertyValues[j];

                    if(pv.Value is string)
                    {
                        if (this.dynamicAssignmentsResolveHandler == null)
                        {
                            throw new InvalidOperationException("No resolveHandler specified - pass an instance " +
                                                                "into the constructor or override the 'ResolveStringValue' method");
                        }

                        bool hasAssignment = dynamicAssignmentsResolveHandler(pv.Value as string, pv.Name, definitionName);

                        // if the property has an assignment then remove it from the properties list because we have alredy dealt with it
                        if (hasAssignment)
                            propertiesToRemove.Add(pv.Name);
                            //pvs.Remove(pv.Name);
                    }
                    else
                    {
                        object newVal = ResolveValue(pv.Value);
                        if (!ObjectUtils.NullSafeEquals(newVal, pv.Value))
                        {
                            pvs.Add(pv.Name, newVal);
                        }                        
                    }
                }

                foreach (string propertyName in propertiesToRemove)
                {
                    pvs.Remove(propertyName);
                }
            }
        }
        
        /// <summary>
        /// Configures the constructor argument ValueHolder.
        /// </summary>
        /// <param name="valueHolder">The vconstructor alue holder.</param>
        protected void ConfigureConstructorArgument(ConstructorArgumentValues.ValueHolder valueHolder)
        {
            object newVal = ResolveValue(valueHolder.Value);
            if (!ObjectUtils.NullSafeEquals(newVal, valueHolder.Value))
            {
                valueHolder.Value = newVal;                
            }
        }

        /// <summary>
        /// Visits the indexed constructor argument values, replacing string values using the
        /// specified IVariableSource.
        /// </summary>
        /// <param name="ias">The indexed argument values.</param>
        protected virtual void VisitIndexedArgumentValues(IDictionary ias)
        {
           foreach (ConstructorArgumentValues.ValueHolder valueHolder in ias.Values)
            {
                
                ConfigureConstructorArgument(valueHolder);
            }
        }

        /// <summary>
        /// Visits the named constructor argument values, replacing string values using the
        /// specified IVariableSource.
        /// </summary>
        /// <param name="nav">The named argument values.</param>
        protected virtual void VisitNamedArgumentValues(IDictionary nav)
        {
            foreach (ConstructorArgumentValues.ValueHolder valueHolder in nav.Values)
            {
                ConfigureConstructorArgument(valueHolder);
            }
        }

        /// <summary>
        /// Visits the generic constructor argument values, replacing string values using
        /// the specified IVariableSource.
        /// </summary>
        /// <param name="gav">The genreic argument values.</param>
        protected virtual void VisitGenericArgumentValues(IList gav)
        {
            foreach (ConstructorArgumentValues.ValueHolder valueHolder in gav)
            {
                ConfigureConstructorArgument(valueHolder);
            }
        }

        /// <summary>
        /// Traverse the given ObjectDefinition object and the MutablePropertyValues
        /// and ConstructorArgumentValues contained in them.
        /// </summary>
        /// <param name="definition">The object definition to traverse.</param>
        public void VisitObjectDefinition(IObjectDefinition definition, string definitionName)
        {
            VisitObjectTypeName(definition);
            VisitPropertyValues(definition, definitionName);

            ConstructorArgumentValues cas = definition.ConstructorArgumentValues;
            if (cas != null)
            {
                VisitIndexedArgumentValues(cas.IndexedArgumentValues);
                VisitNamedArgumentValues(cas.NamedArgumentValues);
                VisitGenericArgumentValues(cas.GenericArgumentValues);
            }
        }

        /// <summary>
        /// calls the <see cref="T:Spring.Objects.Factory.Config.ObjectDefinitionVisitor.ResolveHandler"/> to resolve any variables contained in the raw string.
        /// </summary>
        /// <param name="rawStringValue">the raw string value containing variable placeholders to be resolved</param><exception cref="T:System.InvalidOperationException">If no <see cref="T:Spring.Objects.Factory.Config.IVariableSource"/> has been configured.</exception>
        /// <returns>
        /// the resolved string, having variables being replaced, if any
        /// </returns>
        protected string ResolveStringValue(string rawStringValue)
        {
            return rawStringValue;
        }

        /// <summary>
        /// Visits the ObjectDefinition property ObjectTypeName, replacing string values using 
        /// the specified IVariableSource.
        /// </summary>
        /// <param name="objectDefinition">The object definition.</param>
        protected virtual void VisitObjectTypeName(IObjectDefinition objectDefinition)
        {
            string objectTypeName = objectDefinition.ObjectTypeName;
            if (objectTypeName != null)
            {
                string resolvedName = ResolveStringValue(objectTypeName);
                if (!objectTypeName.Equals(resolvedName))
                {
                    objectDefinition.ObjectTypeName = resolvedName;
                }
            }
        }

        /// <summary>
        /// Visits the ManagedList property ElementTypeName and 
        /// calls <see cref="ResolveValue"/> for list element.
        /// </summary>
        protected virtual void VisitManagedList(ManagedList listVal)
        {
            string elementTypeName = listVal.ElementTypeName;
            if (elementTypeName != null)
            {
                string resolvedName = ResolveStringValue(elementTypeName);
                if (!elementTypeName.Equals(resolvedName))
                {
                    listVal.ElementTypeName = resolvedName;
                }
            }

            for (int i = 0; i < listVal.Count; ++i)
            {
                object oldValue = listVal[i];
                object newValue = ResolveValue(oldValue);
                if (!ObjectUtils.NullSafeEquals(newValue, oldValue))
                {
                    listVal[i] = newValue;
                }
            }
        }

        /// <summary>
        /// Visits the ManagedSet property ElementTypeName and 
        /// calls <see cref="ResolveValue"/> for list element.
        /// </summary>
        protected virtual void VisitManagedSet(ManagedSet setVal)
        {
            string elementTypeName = setVal.ElementTypeName;
            if (elementTypeName != null)
            {
                string resolvedName = ResolveStringValue(elementTypeName);
                if (!elementTypeName.Equals(resolvedName))
                {
                    setVal.ElementTypeName = resolvedName;
                }
            }

            ISet clone = (ISet)setVal.Clone();
            foreach (object oldValue in clone)
            {
                object newValue = ResolveValue(oldValue);
                if (!ObjectUtils.NullSafeEquals(newValue, oldValue))
                {
                    setVal.Remove(oldValue);
                    setVal.Add(newValue);
                }
            }
        }

        /// <summary>
        /// Visits the ManagedSet properties KeyTypeName and ValueTypeName and 
        /// calls <see cref="ResolveValue"/> for dictionary's value element.
        /// </summary>
        protected virtual void VisitManagedDictionary(ManagedDictionary dictVal)
        {
            string keyTypeName = dictVal.KeyTypeName;
            if (keyTypeName != null)
            {
                string resolvedName = ResolveStringValue(keyTypeName);
                if (!keyTypeName.Equals(resolvedName))
                {
                    dictVal.KeyTypeName = resolvedName;
                }
            }

            string valueTypeName = dictVal.ValueTypeName;
            if (valueTypeName != null)
            {
                string resolvedName = ResolveStringValue(valueTypeName);
                if (!valueTypeName.Equals(resolvedName))
                {
                    dictVal.ValueTypeName = resolvedName;
                }
            }

            Hashtable mods = new Hashtable();
            foreach (DictionaryEntry entry in dictVal)
            {
                object oldValue = entry.Value;
                object newValue = ResolveValue(oldValue);
                if (!ObjectUtils.NullSafeEquals(newValue, oldValue))
                {
                    mods[entry.Key] = newValue;
                }
            }
            foreach (DictionaryEntry entry in mods)
            {
                dictVal[entry.Key] = entry.Value;
            }
        }

        /// <summary>
        /// Visits the elements of a NameValueCollection and calls
        /// <see cref="ResolveValue"/> for value of each element.
        /// </summary>
        protected virtual void VisitNameValueCollection(NameValueCollection collection)
        {
            foreach (string key in collection.AllKeys)
            {
                string oldValue = collection[key];
                string newValue = ResolveValue(oldValue) as string;
                if (!ObjectUtils.NullSafeEquals(newValue, oldValue))
                {
                    collection[key] = newValue;
                }
            }
        }

    }
}
